package com.qwc.blog.security.service;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.qwc.blog.entity.User;
import com.qwc.blog.security.constant.SecurityConstant;
import com.qwc.blog.security.entity.LoginUser;
import com.qwc.blog.security.entity.LoginUserVo;
import com.qwc.blog.security.properties.SecurityProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qwc
 * @Date 2021/10/15 14:29
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class LoginService implements InitializingBean {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private SecurityProperties securityProperties;

    public static final String AUTHORITIES_KEY = "user";

    private JwtParser jwtParser;
    private JwtBuilder jwtBuilder;

    /**
     * 登录
     *
     * @param username
     * @param password
     * @return
     */
    public Map<String, Object> login(String username, String password) {
        Map<String, Object> tokenMap = new HashMap<>(16);
        //验证用户
        Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        LoginUser principal = (LoginUser) authenticate.getPrincipal();
        LoginUserVo loginUserVo = new LoginUserVo();
        BeanUtils.copyProperties(principal.getUser(), loginUserVo);
        String token = createToken(loginUserVo, securityProperties.getTokenValidity());
        tokenMap.put(SecurityConstant.TOKEN, token);
        return tokenMap;
    }

    /**
     * 验证 刷新token
     *
     * @param request
     * @return
     */
    public LoginUserVo getLoginUser(HttpServletRequest request) {
        String token = resolveToken(request);
        if (StringUtils.isBlank(token)) {
            return null;
        }
        Claims claims = jwtParser
                .parseClaimsJws(token)
                .getBody();
        LoginUserVo userVo = JSON.parseObject(claims.getSubject(), LoginUserVo.class);
        long now = System.currentTimeMillis();
        long expiration = claims.getExpiration().getTime();
        long l = expiration - now;
        if (l > 0 && l < securityProperties.getDetect()) {
            String renewToken = createToken(userVo, securityProperties.getRenew());
            request.setAttribute(SecurityConstant.RENEW_TOKEN, renewToken);
        }
        return userVo;
    }

    public User getCurrentUser() {
        return ((LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
    }

    public String getCurrentUsername() {
        return getCurrentUser().getUsername();
    }

    /**
     * 解析token
     *
     * @param request
     * @return
     */
    private String resolveToken(HttpServletRequest request) {
        String token = request.getHeader(securityProperties.getHeader());
        if (StringUtils.isNoneBlank(token) && token.startsWith(securityProperties.getTokenStartWith())) {
            // 去掉令牌前缀
            return token.replace(securityProperties.getTokenStartWith(), "");
        } else {
            log.debug("非法Token：{}", token);
        }
        return null;
    }

    /**
     * 创建token
     *
     * @param loginUserVo
     * @param durationHour
     * @return
     */
    private String createToken(LoginUserVo loginUserVo, Integer durationHour) {
        String userVo = JSON.toJSONString(loginUserVo);
        return jwtBuilder
                // 加入ID确保生成的 Token 都不一致
                .setId(IdUtil.simpleUUID())
                .claim(AUTHORITIES_KEY, loginUserVo.getUsername())
                .setSubject(userVo)
                .setExpiration(DateUtils.addMilliseconds(new Date(), durationHour))
                .compact();
    }

    /**
     * 创建jwt工具
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        byte[] keyBytes = Decoders.BASE64.decode(securityProperties.getBase64Secret());
        Key key = Keys.hmacShaKeyFor(keyBytes);
        jwtParser = Jwts.parserBuilder()
                .setSigningKey(key)
                .build();
        jwtBuilder = Jwts.builder()
                .signWith(key, SignatureAlgorithm.HS512);
    }

    public Claims getClaims(String token) {
        return jwtParser
                .parseClaimsJws(token)
                .getBody();
    }

    public Claims getProfile(HttpServletRequest request) {
        String token = resolveToken(request);
        return getClaims(token);
    }

}
